<template>
  <div class="zeus-uploader">
    <div class="trigger" @click="onClickUpload">
      <slot />
    </div>
    <div style="width: 0; height: 0; overflow: hidden" ref="temp"></div>
    <ol class="zeus-uploader-fileList">
      <li v-for="file in fileList" :key="file.name">
        <template v-if="file.status === 'uploading'">
          <svg class="zeus-uploader-loading">
            <use xlink:href="#icon-loading"></use>
          </svg>
        </template>
        <template v-else-if="file.type.indexOf('image') === 0">
          <img
            class="zeus-uploader-image"
            :src="file.url"
            width="30"
            height="30"
          />
        </template>
        <template v-else>
          <div class="zeus-uploader-defaultImage"></div>
        </template>
        <span
          class="zeus-uploader-fileName"
          :class="{ [file.status]: file.status }"
          >{{ file.name }}</span
        >
        <span class="zeus-uploader-remove" @click="onRemoveFile(file)">x</span>
      </li>
    </ol>
  </div>
</template>

<script lang="ts">
import { ref } from '@vue/reactivity'
export default {
  /**
   * @description 本组件用于上传单文件或者多文件图片。
   * @property {String} name 服务器需要接收一个值为file的name，用于input上
   * @property {String} action 需要接收一个存放图片的服务器
   * @property {String} method 请求的方法，默认为POST请求
   * @property {Function} parseResponse 请求图片成功的回调，需要返回处理完成的图片路径
   * @property {Array} fileList 图片列表
   * @property {Number} sizeLimit 图片的最大尺寸
   */
  props: {
    name: {
      type: String,
      required: true,
    },
    action: {
      type: String,
      required: true,
    },
    method: {
      type: String,
      default: 'POST',
    },
    parseResponse: {
      type: Function,
      required: true,
    },
    fileList: {
      type: Array,
      default: () => [],
    },
    sizeLimit: {
      type: Number,
    },
  },
  setup(props, context) {
    let temp = ref<HTMLDivElement>(null)
    // 用户点击上传按钮
    let onClickUpload = () => {
      let input = createInput()
      input.addEventListener('change', () => {
        uploadFiles(input.files)
        input.remove()
      })
      input.click()
    }
    // 删除文件
    const onRemoveFile = (file) => {
      let answer = window.confirm('确定删除此文件吗？')
      if (answer) {
        let copy = [...props.fileList]
        let index = copy.indexOf(file)
        copy.splice(index, 1)
        context.emit('update:fileList', copy)
      }
    }
    // 创建input插入temp
    const createInput = () => {
      temp.value.innerHTML = ''
      let input = document.createElement('input')
      input.type = 'file'
      input.accept = 'image/*'
      input.multiple = true
      temp.value.appendChild(input)
      return input
    }
    // 上传文件
    const uploadFiles = (rawFiles) => {
      let newNames = []
      for (let i = 0; i < rawFiles.length; i++) {
        let rawFile = rawFiles[i]
        let { name, type, size } = rawFile
        let newName = generateName(name)
        newNames[i] = newName
      }
      if (!beforeUploadFiles(rawFiles, newNames)) {
        return
      }
      for (let i = 0; i < rawFiles.length; i++) {
        let rawFile = rawFiles[i]
        let newName = newNames[i]
        let formData = new FormData()
        formData.append(props.name, rawFile)
        doUploadFiles(
          formData,
          (response) => {
            let url = props.parseResponse(response)
            afterUploadFile(newName, url)
          },
          (xhr) => {
            uploadeFail(xhr, newName)
          }
        )
      }
    }

    // 上传文件前状态为uploading
    const beforeUploadFiles = (rawFiles, newNames) => {
      rawFiles = Array.from(rawFiles)
      for (let i = 0; i < rawFiles.length; i++) {
        let { type, size } = rawFiles[i]
        if (size > props.sizeLimit) {
          context.emit('error', '文件大于2MB')
          return false
        }
      }

      let x = rawFiles.map((rawFile, i) => {
        let { type, size } = rawFile
        return { name: newNames[i], type, size, status: 'uploading' }
      })
      context.emit('update:fileList', [...props.fileList, ...x])
      return true
    }
    // 上传文件成功后状态为success
    const afterUploadFile = (newName, url) => {
      let file = props.fileList.filter((f) => f.name === newName)[0]
      let index = props.fileList.indexOf(file)
      let fileCopy = JSON.parse(JSON.stringify(file))
      fileCopy.url = url.value
      fileCopy.status = 'success'
      let fileListCopy = [...props.fileList]
      fileListCopy.splice(index, 1, fileCopy)
      context.emit('update:fileList', fileListCopy)
    }
    // 生成唯一文件名
    const generateName = (name) => {
      while (props.fileList.filter((f) => f.name === name).length > 0) {
        let doIndex = name.lastIndexOf('.')
        let nameWithoutExtension = name.substring(0, doIndex)
        let extension = name.substring(doIndex)
        name = nameWithoutExtension + '(1)' + extension
      }
      return name
    }
    // 上传失败
    const uploadeFail = (xhr, newName) => {
      let file = props.fileList.filter((f) => f.name === newName)[0]
      let index = props.fileList.indexOf(file)
      let fileCopy = JSON.parse(JSON.stringify(file))
      fileCopy.status = 'fail'
      let fileListCopy = [...props.fileList]
      fileListCopy.splice(index, 1, fileCopy)
      context.emit('update:fileList', fileListCopy)
      let error = ''
      if (xhr.status === 0) {
        error = '网络无法连接'
      }
      context.emit('error', error)
    }
    // 将文件上传到服务器
    const doUploadFiles = (formData, success, fail) => {
      let xhr = new XMLHttpRequest()
      xhr.open(props.method, props.action)
      xhr.onload = () => {
        success(xhr.response)
      }
      xhr.onerror = () => {
        fail(xhr, xhr.status)
      }
      xhr.send(formData)
    }

    return {
      temp,
      onClickUpload,
      onRemoveFile,
    }
  },
}
</script>

<style lang="scss">
.zeus-uploader {
  &-fileList {
    list-style: none;
    li {
      display: flex;
      align-items: center;
      margin: 8px 0;
      border: 1px solid #eee;
    }
  }

  &-defaultImage {
    width: 32px;
    height: 32px;
    border: 1px solid #eee;
  }

  &-image {
    margin-right: 20px;
    display: block;
    border: none;
  }

  &-fileName {
    max-width: 10em;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    margin-right: auto;
    &.success {
      color: green;
    }
    &.fail {
      color: red;
    }
  }

  &-remove {
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    &:hover {
      background: #eee;
    }
  }
  &-loading {
    width: 16px;
    height: 16px;
    margin: 0 10px;
    animation: loading-spin 1s infinite linear;
  }
  @keyframes loading-spin {
    0% {
      transform: rotate(0deg);
    }
    100% {
      transform: rotate(360deg);
    }
  }
}
</style>
